/*
 * Copyright (C) 2005-2011 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008-2011 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010-2011 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "gamePCH.h"
#include "Common.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Opcodes.h"
#include "Log.h"
#include "Corpse.h"
#include "Player.h"
#include "Vehicle.h"
#include "SpellAuras.h"
#include "MapManager.h"
#include "Transport.h"
#include "Battleground.h"
#include "WaypointMovementGenerator.h"
#include "InstanceSaveMgr.h"
#include "ObjectMgr.h"

void WorldSession::HandleMoveWorldportAckOpcode(WorldPacket & /*recv_data*/) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: got MSG_MOVE_WORLDPORT_ACK.");
	HandleMoveWorldportAckOpcode();
}

void WorldSession::HandleMoveWorldportAckOpcode() {
	// ignore unexpected far teleports
	if (!GetPlayer()->IsBeingTeleportedFar())
		return;

	// get the teleport destination
	WorldLocation &loc = GetPlayer()->GetTeleportDest();

	// possible errors in the coordinate validity check
	if (!MapManager::IsValidMapCoord(loc)) {
		LogoutPlayer(false);
		return;
	}

	// get the destination map entry, not the current one, this will fix homebind and reset greeting
	MapEntry const* mEntry = sMapStore.LookupEntry(loc.GetMapId());
	InstanceTemplate const* mInstance =
			sObjectMgr->GetInstanceTemplate(loc.GetMapId());

			// reset instance validity, except if going to an instance inside an instance
					if (GetPlayer()->m_InstanceValid == false && !mInstance)
					GetPlayer()->m_InstanceValid = true;

					GetPlayer()->SetSemaphoreTeleportFar(false);

					Map * oldMap = GetPlayer()->GetMap();
					ASSERT(oldMap);
					if (GetPlayer()->IsInWorld())
					{
						sLog->outCrash("Player is still in world when teleported from map %u! to new map %u", oldMap->GetId(), loc.GetMapId());
						oldMap->Remove(GetPlayer(), false);
					}

					// relocate the player to the teleport destination
					Map * newMap = sMapMgr->CreateMap(loc.GetMapId(), GetPlayer(), 0);
					// the CanEnter checks are done in TeleporTo but conditions may change
					// while the player is in transit, for example the map may get full
					if (!newMap || !newMap->CanEnter(GetPlayer()))
					{
						sLog->outError("Map %d could not be created for player %d, porting player to homebind", loc.GetMapId(), GetPlayer()->GetGUIDLow());
						GetPlayer()->TeleportTo(GetPlayer()->m_homebindMapId, GetPlayer()->m_homebindX, GetPlayer()->m_homebindY, GetPlayer()->m_homebindZ, GetPlayer()->GetOrientation());
						return;
					}
					else
					GetPlayer()->Relocate(&loc);

					GetPlayer()->ResetMap();
					GetPlayer()->SetMap(newMap);

					GetPlayer()->SendInitialPacketsBeforeAddToMap();
					if (!GetPlayer()->GetMap()->Add(GetPlayer()))
					{
						sLog->outError("WORLD: failed to teleport player %s (%d) to map %d because of unknown reason!", GetPlayer()->GetName(), GetPlayer()->GetGUIDLow(), loc.GetMapId());
						GetPlayer()->ResetMap();
						GetPlayer()->SetMap(oldMap);
						GetPlayer()->TeleportTo(GetPlayer()->m_homebindMapId, GetPlayer()->m_homebindX, GetPlayer()->m_homebindY, GetPlayer()->m_homebindZ, GetPlayer()->GetOrientation());
						return;
					}

					// battleground state prepare (in case join to BG), at relogin/tele player not invited
					// only add to bg group and object, if the player was invited (else he entered through command)
					if (_player->InBattleground())
					{
						// cleanup setting if outdated
						if (!mEntry->IsBattlegroundOrArena())
						{
							// We're not in BG
							_player->SetBattlegroundId(0, BATTLEGROUND_TYPE_NONE);
							// reset destination bg team
							_player->SetBGTeam(0);
						}
						// join to bg case
						else if (Battleground *bg = _player->GetBattleground())
						{
							if (_player->IsInvitedForBattlegroundInstance(_player->GetBattlegroundId()))
							bg->AddPlayer(_player);
						}
					}

					GetPlayer()->SendInitialPacketsAfterAddToMap();

					// flight fast teleport case
					if (GetPlayer()->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE)
					{
						if (!_player->InBattleground())
						{
							// short preparations to continue flight
							FlightPathMovementGenerator* flight = (FlightPathMovementGenerator*)(GetPlayer()->GetMotionMaster()->top());
							flight->Initialize(*GetPlayer());
							return;
						}

						// battleground state prepare, stop flight
						GetPlayer()->GetMotionMaster()->MovementExpired();
						GetPlayer()->CleanupAfterTaxiFlight();
					}

					// resurrect character at enter into instance where his corpse exist after add to map
					Corpse *corpse = GetPlayer()->GetCorpse();
					if (corpse && corpse->GetType() != CORPSE_BONES && corpse->GetMapId() == GetPlayer()->GetMapId())
					{
						if (mEntry->IsDungeon())
						{
							GetPlayer()->ResurrectPlayer(0.5f, false);
							GetPlayer()->SpawnCorpseBones();
						}
					}

					bool allowMount = !mEntry->IsDungeon() || mEntry->IsBattlegroundOrArena();
					if (mInstance)
					{
						Difficulty diff = GetPlayer()->GetDifficulty(mEntry->IsRaid());
						if (MapDifficulty const* mapDiff = GetMapDifficultyData(mEntry->MapID, diff))
						{
							if (mapDiff->resetTime)
							{
								if (time_t timeReset = sInstanceSaveMgr->GetResetTimeFor(mEntry->MapID, diff))
								{
									uint32 timeleft = uint32(timeReset - time(NULL));
									GetPlayer()->SendInstanceResetWarning(mEntry->MapID, diff, timeleft);
								}
							}
						}
						allowMount = mInstance->allowMount;
					}

					// mount allow check
					if (!allowMount)
					_player->RemoveAurasByType(SPELL_AURA_MOUNTED);

					// update zone immediately, otherwise leave channel will cause crash in mtmap
					uint32 newzone, newarea
;GetPlayer()->GetZoneAndAreaId	(newzone, newarea);
GetPlayer	()->
UpdateZone(newzone,newarea)	;

	// honorless target
if(GetPlayer
	()->
pvpInfo.	inHostileArea)
	GetPlayer()->CastSpell(GetPlayer(), 2479, true);

	// in friendly area
	else if (GetPlayer()->IsPvP() && !GetPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP))
	GetPlayer()->UpdatePvP(false, false);

	// resummon pet
	GetPlayer()->ResummonPetTemporaryUnSummonedIfAny();

	//lets process all delayed operations on successful teleport
	GetPlayer()->ProcessDelayedOperations();
}

void WorldSession::HandleMoveTeleportAck(WorldPacket& recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "MSG_MOVE_TELEPORT_ACK");
	uint64 guid;

	recv_data.readPackGUID(guid);

	uint32 flags, time;
	recv_data >> flags >> time;
	sLog->outStaticDebug("Guid " UI64FMTD, guid);
	sLog->outStaticDebug("Flags %u, time %u", flags, time/IN_MILLISECONDS);

	Unit *mover = _player->m_mover;
	Player *plMover = mover->GetTypeId() == TYPEID_PLAYER ? (Player*)mover : NULL;

	if (!plMover || !plMover->IsBeingTeleportedNear())
	return;

	if (guid != plMover->GetGUID())
	return;

	plMover->SetSemaphoreTeleportNear(false);

	uint32 old_zone = plMover->GetZoneId();

	WorldLocation const& dest = plMover->GetTeleportDest();

	plMover->SetPosition(dest, true);

	uint32 newzone, newarea;
	plMover->GetZoneAndAreaId(newzone, newarea);
	plMover->UpdateZone(newzone, newarea);

	// new zone
	if (old_zone != newzone)
	{
		// honorless target
		if (plMover->pvpInfo.inHostileArea)
		plMover->CastSpell(plMover, 2479, true);

		// in friendly area
		else if (plMover->IsPvP() && !plMover->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP))
		plMover->UpdatePvP(false, false);
	}

	// resummon pet
	GetPlayer()->ResummonPetTemporaryUnSummonedIfAny();

	//lets process all delayed operations on successful teleport
	GetPlayer()->ProcessDelayedOperations();
}

void WorldSession::HandleMovementOpcodes(WorldPacket & recv_data) {
	uint32 opcode = recv_data.GetOpcode();
	recv_data.hexlike();

	Unit *mover = _player->m_mover;

	ASSERT(mover != NULL); // there must always be a mover

	Player *plMover =
			mover->GetTypeId() == TYPEID_PLAYER ? (Player*) mover : NULL;

	// ignore, waiting processing in WorldSession::HandleMoveWorldportAckOpcode and WorldSession::HandleMoveTeleportAck
	if (plMover && plMover->IsBeingTeleported()) {
		recv_data.rpos(recv_data.wpos()); // prevent warnings spam
		return;
	}

	/* extract packet */
	uint64 guid;

	recv_data.readPackGUID(guid);

	MovementInfo movementInfo;
	movementInfo.guid = guid;
	ReadMovementInfo(recv_data, &movementInfo);

	recv_data.rpos(recv_data.wpos()); // prevent warnings spam

	// prevent tampered movement data
	if (guid != mover->GetGUID())
		return;

	if (!movementInfo.pos.IsPositionValid()) {
		recv_data.rpos(recv_data.wpos()); // prevent warnings spam
		return;
	}

	/* handle special cases */
	if (movementInfo.flags & MOVEMENTFLAG_ONTRANSPORT) {
		// transports size limited
		// (also received at zeppelin leave by some reason with t_* as absolute in continent coordinates, can be safely skipped)
		if (movementInfo.t_pos.GetPositionX() > 50
				|| movementInfo.t_pos.GetPositionY() > 50
				|| movementInfo.t_pos.GetPositionZ() > 50) {
			recv_data.rpos(recv_data.wpos()); // prevent warnings spam
			return;
		}

		if (!Trinity::IsValidMapCoord(
				movementInfo.pos.GetPositionX()
						+ movementInfo.t_pos.GetPositionX(),
				movementInfo.pos.GetPositionY()
						+ movementInfo.t_pos.GetPositionY(),
				movementInfo.pos.GetPositionZ()
						+ movementInfo.t_pos.GetPositionZ(),
				movementInfo.pos.GetOrientation()
						+ movementInfo.t_pos.GetOrientation())) {
			recv_data.rpos(recv_data.wpos()); // prevent warnings spam
			return;
		}

		// if we boarded a transport, add us to it
		if (plMover && !plMover->GetTransport()) {
			// elevators also cause the client to send MOVEMENTFLAG_ONTRANSPORT - just unmount if the guid can be found in the transport list
			for (MapManager::TransportSet::const_iterator iter =
					sMapMgr->m_Transports.begin();
					iter != sMapMgr->m_Transports.end(); ++iter) {
				if ((*iter)->GetGUID() == movementInfo.t_guid) {
					plMover->m_transport = (*iter);
					(*iter)->AddPassenger(plMover);
					break;
				}
			}
		}

		if (!mover->GetTransport() && !mover->GetVehicle()) {
			GameObject *go = mover->GetMap()->GetGameObject(
					movementInfo.t_guid);
			if (!go || go->GetGoType() != GAMEOBJECT_TYPE_TRANSPORT)
				movementInfo.flags &= ~MOVEMENTFLAG_ONTRANSPORT;
		}
	} else if (plMover && plMover->GetTransport()) // if we were on a transport, leave
			{
		plMover->m_transport->RemovePassenger(plMover);
		plMover->m_transport = NULL;
		movementInfo.t_pos.Relocate(0.0f, 0.0f, 0.0f, 0.0f);
		movementInfo.t_time = 0;
		movementInfo.t_seat = -1;
	}

	// fall damage generation (ignore in flight case that can be triggered also at lags in moment teleportation to another map).
	if (opcode == MSG_MOVE_FALL_LAND && plMover && !plMover->isInFlight())
		plMover->HandleFall(movementInfo);

	if (plMover
			&& ((movementInfo.flags & MOVEMENTFLAG_SWIMMING) != 0)
					!= plMover->IsInWater()) {
		// now client not include swimming flag in case jumping under water
		plMover->SetInWater(
				!plMover->IsInWater()
						|| plMover->GetBaseMap()->IsUnderWater(
								movementInfo.pos.GetPositionX(),
								movementInfo.pos.GetPositionY(),
								movementInfo.pos.GetPositionZ()));
	}

	/*----------------------*/

	/* process position-change */
	WorldPacket data(opcode, recv_data.size());
	movementInfo.time = getMSTime();
	movementInfo.guid = mover->GetGUID();
	WriteMovementInfo(&data, &movementInfo);
	mover->SendMessageToSet(&data, _player);

	mover->m_movementInfo = movementInfo;

	// this is almost never true (not sure why it is sometimes, but it is), normally use mover->IsVehicle()
	if (mover->GetVehicle()) {
		mover->SetOrientation(movementInfo.pos.GetOrientation());
		return;
	}

	mover->SetPosition(movementInfo.pos);

	if (plMover) // nothing is charmed, or player charmed
	{
		if (plMover->GetEmoteState() != 0 && opcode == MSG_MOVE_START_FORWARD
				&& opcode != MSG_MOVE_SET_FACING
				&& opcode != MSG_MOVE_START_TURN_LEFT
				&& opcode != MSG_MOVE_START_TURN_RIGHT
				&& opcode != MSG_MOVE_STOP_TURN)
			plMover->SetEmoteState(0);

		plMover->UpdateFallInformationIfNeed(movementInfo, opcode);

		if (movementInfo.pos.GetPositionZ() < -5000.0f) {
			if (!(plMover->InBattleground() && plMover->GetBattleground()
					&& plMover->GetBattleground()->HandlePlayerUnderMap(_player))) {
				// NOTE: this is actually called many times while falling
				// even after the player has been teleported away
				// TODO: discard movement packets after the player is rooted
				if (plMover->isAlive()) {
					plMover->EnvironmentalDamage(DAMAGE_FALL_TO_VOID,
							GetPlayer()->GetMaxHealth());
					// pl can be alive if GM/etc
					if (!plMover->isAlive()) {
						// change the death state to CORPSE to prevent the death timer from
						// starting in the next player update
						plMover->KillPlayer();
						plMover->BuildPlayerRepop();
					}
				}

				// cancel the death timer here if started
				plMover->RepopAtGraveyard();
			}
		}
        else if (movementInfo.pos.GetPositionZ() < -50.0f)
        {
            if (plMover->InBattleground())
                if (Battleground* bg = plMover->GetBattleground())
                    if (bg->isArena())
                        bg->HandlePlayerUnderMap(_player);
        }		
	}
}

void WorldSession::HandleForceSpeedChangeAck(WorldPacket &recv_data) {
	uint32 opcode = recv_data.GetOpcode();
	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Recvd %s (%u, 0x%X) opcode",
			LookupOpcodeName(opcode), opcode, opcode);

	/* extract packet */
	uint64 guid;
	uint32 unk1;
	float newspeed;

	recv_data.readPackGUID(guid);

	// now can skip not our packet
	if (_player->GetGUID() != guid) {
		recv_data.rpos(recv_data.wpos()); // prevent warnings spam
		return;
	}

	// continue parse packet

	recv_data >> unk1; // counter or moveEvent

	MovementInfo movementInfo;
	movementInfo.guid = guid;
	ReadMovementInfo(recv_data, &movementInfo);

	recv_data >> newspeed;
	/*----------------*/

	// client ACK send one packet for mounted/run case and need skip all except last from its
	// in other cases anti-cheat check can be fail in false case
	UnitMoveType move_type;
	UnitMoveType force_move_type;

	static char const* move_type_name[MAX_MOVE_TYPE] = { "Walk", "Run",
			"RunBack", "Swim", "SwimBack", "TurnRate", "Flight", "FlightBack",
			"PitchRate" };

	switch (opcode) {
	case CMSG_FORCE_WALK_SPEED_CHANGE_ACK:
		move_type = MOVE_WALK;
		force_move_type = MOVE_WALK;
		break;
	case CMSG_FORCE_RUN_SPEED_CHANGE_ACK:
		move_type = MOVE_RUN;
		force_move_type = MOVE_RUN;
		break;
	case CMSG_FORCE_RUN_BACK_SPEED_CHANGE_ACK:
		move_type = MOVE_RUN_BACK;
		force_move_type = MOVE_RUN_BACK;
		break;
	case CMSG_FORCE_SWIM_SPEED_CHANGE_ACK:
		move_type = MOVE_SWIM;
		force_move_type = MOVE_SWIM;
		break;
	case CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK:
		move_type = MOVE_SWIM_BACK;
		force_move_type = MOVE_SWIM_BACK;
		break;
	case CMSG_FORCE_TURN_RATE_CHANGE_ACK:
		move_type = MOVE_TURN_RATE;
		force_move_type = MOVE_TURN_RATE;
		break;
	case CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK:
		move_type = MOVE_FLIGHT;
		force_move_type = MOVE_FLIGHT;
		break;
	case CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK:
		move_type = MOVE_FLIGHT_BACK;
		force_move_type = MOVE_FLIGHT_BACK;
		break;
	case CMSG_FORCE_PITCH_RATE_CHANGE_ACK:
		move_type = MOVE_PITCH_RATE;
		force_move_type = MOVE_PITCH_RATE;
		break;
	default:
		sLog->outError(
				"WorldSession::HandleForceSpeedChangeAck: Unknown move type opcode: %u",
				opcode);
		return;
	}

	// skip all forced speed changes except last and unexpected
	// in run/mounted case used one ACK and it must be skipped.m_forced_speed_changes[MOVE_RUN} store both.
	if (_player->m_forced_speed_changes[force_move_type] > 0) {
		--_player->m_forced_speed_changes[force_move_type];
		if (_player->m_forced_speed_changes[force_move_type] > 0)
			return;
	}

	if (!_player->GetTransport()
			&& fabs(_player->GetSpeed(move_type) - newspeed) > 0.01f) {
		if (_player->GetSpeed(move_type) > newspeed) // must be greater - just correct
				{
			sLog->outError(
					"%sSpeedChange player %s is NOT correct (must be %f instead %f), force set to correct value",
					move_type_name[move_type], _player->GetName(),
					_player->GetSpeed(move_type), newspeed);
			_player->SetSpeed(move_type, _player->GetSpeedRate(move_type),
					true);
		} else // must be lesser - cheating
		{
			sLog->outBasic(
					"Player %s from account id %u kicked for incorrect speed (must be %f instead %f)",
					_player->GetName(), _player->GetSession()->GetAccountId(),
					_player->GetSpeed(move_type), newspeed);
			_player->GetSession()->KickPlayer();
		}
	}
}

void WorldSession::HandleSetActiveMoverOpcode(WorldPacket &recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Recvd CMSG_SET_ACTIVE_MOVER");

	uint64 guid;
	recv_data >> guid;

	// do not re-set the active mover if it didn't change
	if (guid == _player->m_mover->GetGUID())
		return;
	// Anti-cheat check
	if (guid != _player->GetCharmGUID() && guid != _player->GetGUID()) {
		sLog->outError(
				"Player %s is trying to change mover to an invalid value!",
				_player->GetName());
		GetPlayer()->SetMover(GetPlayer());
		return;
	}

	if (GetPlayer()->IsInWorld()) {
		if (Unit *mover = ObjectAccessor::GetUnit(*GetPlayer(), guid)) {
			GetPlayer()->SetMover(mover);
			if (mover != GetPlayer() && mover->canFly()) {
				WorldPacket data(SMSG_MOVE_SET_CAN_FLY, 12, true);
				data.append(mover->GetPackGUID());
				data << uint32(0);
				SendPacket(&data);
			}
		} else {
			sLog->outError(
					"HandleSetActiveMoverOpcode: incorrect mover guid: mover is " UI64FMTD " and should be " UI64FMTD,
					guid, _player->m_mover->GetGUID());
			GetPlayer()->SetMover(GetPlayer());
		}
	}
}

void WorldSession::HandleMoveNotActiveMover(WorldPacket &recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Recvd CMSG_MOVE_NOT_ACTIVE_MOVER");

	uint64 old_mover_guid;
	recv_data.readPackGUID(old_mover_guid);

	MovementInfo mi;
	mi.guid = old_mover_guid;
	ReadMovementInfo(recv_data, &mi);

	_player->m_movementInfo = mi;
}

void WorldSession::HandleDismissControlledVehicle(WorldPacket &recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Recvd CMSG_DISMISS_CONTROLLED_VEHICLE");
	recv_data.hexlike();

	uint64 vehicleGUID = _player->GetCharmGUID();

	if (!vehicleGUID) // something wrong here...
	{
		recv_data.rpos(recv_data.wpos()); // prevent warnings spam
		return;
	}

	uint64 guid;

	recv_data.readPackGUID(guid);

	MovementInfo mi;
	mi.guid = guid;
	ReadMovementInfo(recv_data, &mi);

	_player->m_movementInfo = mi;

	_player->ExitVehicle();
}

void WorldSession::HandleChangeSeatsOnControlledVehicle(
		WorldPacket &recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Recvd CMSG_CHANGE_SEATS_ON_CONTROLLED_VEHICLE");
	recv_data.hexlike();

	Unit* vehicle_base = GetPlayer()->GetVehicleBase();
	if (!vehicle_base)
		return;

	VehicleSeatEntry const* seat =
			GetPlayer()->GetVehicle()->GetSeatForPassenger(GetPlayer());
	if (!seat->CanSwitchFromSeat()) {
		sLog->outError(
				"HandleChangeSeatsOnControlledVehicle, Opcode: %u, Player %u tried to switch seats but current seatflags %u don't permit that.",
				recv_data.GetOpcode(), GetPlayer()->GetGUIDLow(),
				seat->m_flags);
		return;
	}

	switch (recv_data.GetOpcode()) {
	case CMSG_REQUEST_VEHICLE_PREV_SEAT:
		GetPlayer()->ChangeSeat(-1, false);
		break;
	case CMSG_REQUEST_VEHICLE_NEXT_SEAT:
		GetPlayer()->ChangeSeat(-1, true);
		break;
	case CMSG_CHANGE_SEATS_ON_CONTROLLED_VEHICLE: {
		uint64 guid; // current vehicle guid
		recv_data.readPackGUID(guid);

		ReadMovementInfo(recv_data, &vehicle_base->m_movementInfo);

		uint64 accessory; //  accessory guid
		recv_data.readPackGUID(accessory);

		int8 seatId;
		recv_data >> seatId;

		if (vehicle_base->GetGUID() != guid)
			return;

		if (!accessory)
			GetPlayer()->ChangeSeat(-1, seatId > 0); // prev/next
		else if (Unit *vehUnit = Unit::GetUnit(*GetPlayer(), accessory)) {
			if (Vehicle *vehicle = vehUnit->GetVehicleKit())
				if (vehicle->HasEmptySeat(seatId))
					GetPlayer()->EnterVehicle(vehicle, seatId);
		}
	}
		break;
	case CMSG_REQUEST_VEHICLE_SWITCH_SEAT: {
		uint64 guid; // current vehicle guid
		recv_data.readPackGUID(guid);

		int8 seatId;
		recv_data >> seatId;

		if (vehicle_base->GetGUID() == guid)
			GetPlayer()->ChangeSeat(seatId);
		else if (Unit *vehUnit = Unit::GetUnit(*GetPlayer(), guid))
			if (Vehicle *vehicle = vehUnit->GetVehicleKit())
				if (vehicle->HasEmptySeat(seatId))
					GetPlayer()->EnterVehicle(vehicle, seatId);
	}
		break;
	default:
		break;
	}
}

void WorldSession::HandleEnterPlayerVehicle(WorldPacket &data) {
	// Read guid
	uint64 guid;
	data >> guid;

	if (Player* pl=ObjectAccessor::FindPlayer(guid)) {
		if (!pl->GetVehicleKit())
			return;
		if (!pl->IsInRaidWith(_player))
			return;
		if (!pl->IsWithinDistInMap(_player, INTERACTION_DISTANCE))
			return;
		_player->EnterVehicle(pl);
	}
}

void WorldSession::HandleEjectPassenger(WorldPacket &data) {
	Vehicle* vehicle = _player->GetVehicleKit();
	if (!vehicle) {
		sLog->outError("HandleEjectPassenger: Player %u is not in a vehicle!",
				GetPlayer()->GetGUIDLow());
		return;
	}

	uint64 guid;
	data >> guid;

	if (IS_PLAYER_GUID(guid)) {
		Player *plr = ObjectAccessor::FindPlayer(guid);
		if (!plr) {
			sLog->outError(
					"Player %u tried to eject player %u from vehicle, but the latter was not found in world!",
					GetPlayer()->GetGUIDLow(), GUID_LOPART(guid));
			return;
		}

		if (!plr->IsOnVehicle(vehicle->GetBase())) {
			sLog->outError(
					"Player %u tried to eject player %u, but they are not in the same vehicle",
					GetPlayer()->GetGUIDLow(), GUID_LOPART(guid));
			return;
		}

		VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(plr);
		ASSERT(seat);
		if (seat->IsEjectable())
			plr->ExitVehicle();
		else
			sLog->outError(
					"Player %u attempted to eject player %u from non-ejectable seat.",
					GetPlayer()->GetGUIDLow(), GUID_LOPART(guid));
	}

	else if (IS_CREATURE_GUID(guid)) {
		Unit *unit = ObjectAccessor::GetUnit(*_player, guid);
		if (!unit) // creatures can be ejected too from player mounts
		{
			sLog->outError(
					"Player %u tried to eject creature guid %u from vehicle, but the latter was not found in world!",
					GetPlayer()->GetGUIDLow(), GUID_LOPART(guid));
			return;
		}

		if (!unit->IsOnVehicle(vehicle->GetBase())) {
			sLog->outError(
					"Player %u tried to eject unit %u, but they are not in the same vehicle",
					GetPlayer()->GetGUIDLow(), GUID_LOPART(guid));
			return;
		}

		VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(unit);
		ASSERT(seat);
		if (seat->IsEjectable()) {
			ASSERT(GetPlayer() == vehicle->GetBase());
			unit->ExitVehicle();
			unit->ToCreature()->DespawnOrUnsummon(1000);
			ASSERT(!vehicle->GetBase()->IsOnVehicle(unit));
		} else
			sLog->outError(
					"Player %u attempted to eject creature GUID %u from non-ejectable seat.",
					GetPlayer()->GetGUIDLow(), GUID_LOPART(guid));
	} else
		sLog->outError(
				"HandleEjectPassenger: Player %u tried to eject invalid GUID "UI64FMTD,
				GetPlayer()->GetGUIDLow(), guid);
}

void WorldSession::HandleRequestVehicleExit(WorldPacket &recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO,
			"WORLD: Recvd CMSG_REQUEST_VEHICLE_EXIT");
	recv_data.hexlike();

	if (Vehicle* vehicle = GetPlayer()->GetVehicle()) {
		if (VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(GetPlayer()))
			if (seat->CanEnterOrExit())
				GetPlayer()->ExitVehicle();
			else
				sLog->outError(
						"Player %u tried to exit vehicle, but seatflags %u (ID: %u) don't permit that.",
						GetPlayer()->GetGUIDLow(), seat->m_ID, seat->m_flags);
	}
}

void WorldSession::HandleMountSpecialAnimOpcode(WorldPacket& /*recv_data*/) {
	WorldPacket data(SMSG_MOUNTSPECIAL_ANIM, 8);
	data << uint64(GetPlayer()->GetGUID());

	GetPlayer()->SendMessageToSet(&data, false);
}

void WorldSession::HandleMoveKnockBackAck(WorldPacket & recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_MOVE_KNOCK_BACK_ACK");

	uint64 guid; // guid - unused
	recv_data.readPackGUID(guid);

	recv_data.read_skip<uint32>(); // unk

	MovementInfo movementInfo;
	ReadMovementInfo(recv_data, &movementInfo);
}

void WorldSession::HandleMoveHoverAck(WorldPacket& recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_MOVE_HOVER_ACK");

	uint64 guid; // guid - unused
	recv_data.readPackGUID(guid);

	recv_data.read_skip<uint32>(); // unk

	MovementInfo movementInfo;
	ReadMovementInfo(recv_data, &movementInfo);

	recv_data.read_skip<uint32>(); // unk2
}

void WorldSession::HandleMoveWaterWalkAck(WorldPacket& recv_data) {
	sLog->outDebug(LOG_FILTER_NETWORKIO, "CMSG_MOVE_WATER_WALK_ACK");

	uint64 guid; // guid - unused
	recv_data.readPackGUID(guid);

	recv_data.read_skip<uint32>(); // unk

	MovementInfo movementInfo;
	ReadMovementInfo(recv_data, &movementInfo);

	recv_data.read_skip<uint32>(); // unk2
}

void WorldSession::HandleSummonResponseOpcode(WorldPacket& recv_data) {
	if (!_player->isAlive() || _player->isInCombat())
		return;

	uint64 summoner_guid;
	bool agree;
	recv_data >> summoner_guid;
	recv_data >> agree;

	_player->SummonIfPossible(agree);
}
